﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;

namespace Smled
{
    public class BrushRule
    {
        private List<Tile> toPaint = new List<Tile>();
        private int[,] pattern = new int[3, 3];
        private ManagedBrush brush = null;
        private INIGroup iniDesc = null;
        private int[] ids = null;

        public BrushRule(ManagedBrush b)
        {
            this.brush = b;
            pattern[1, 1] = 1;
        }

        public BrushRule(ManagedBrush b, INIGroup source)
        {
            this.brush = b;
            iniDesc = source;
            ReadFromSource();
        }

        private void ReadFromSource()
        {
            ids = iniDesc.getIntList("Tiles", -1);
            toPaint = null;
            pattern[0, 0] = iniDesc.getInteger("00", 0);
            pattern[0, 1] = iniDesc.getInteger("01", 0);
            pattern[0, 2] = iniDesc.getInteger("02", 0);
            pattern[1, 0] = iniDesc.getInteger("10", 0);
            pattern[1, 1] = iniDesc.getInteger("11", 0);
            pattern[1, 2] = iniDesc.getInteger("12", 0);
            pattern[2, 0] = iniDesc.getInteger("20", 0);
            pattern[2, 1] = iniDesc.getInteger("21", 0);
            pattern[2, 2] = iniDesc.getInteger("22", 0);
        }

        public void UpdateSource()
        {
            if (iniDesc == null) iniDesc = new INIGroup("BrushRule");

            string list = "";
            for (int i = 0; i < toPaint.Count; ++i)
            {
                list += toPaint[i].ID.ToString();
                if (i < toPaint.Count - 1) list += ";";
            }
            
            iniDesc.set("Tiles", list);
            iniDesc.set("00", pattern[0, 0].ToString());
            iniDesc.set("01", pattern[0, 1].ToString());
            iniDesc.set("02", pattern[0, 2].ToString());
            iniDesc.set("10", pattern[1, 0].ToString());
            iniDesc.set("11", pattern[1, 1].ToString());
            iniDesc.set("12", pattern[1, 2].ToString());
            iniDesc.set("20", pattern[2, 0].ToString());
            iniDesc.set("21", pattern[2, 1].ToString());
            iniDesc.set("22", pattern[2, 2].ToString());
        }

        public INIGroup INIDescription { get { return iniDesc; } }
        public List<Tile> Tiles { get { return toPaint; } }
        public int[,] Pattern { get { return pattern; } }

        public void FillIn(TileSet ts)
        {
            toPaint = new List<Tile>();
            for (int i = 0; i < ids.Length; ++i)
                toPaint.Add(ts.GetTile(ids[i]));
        }

        public bool Match(TileMap map, int x, int y)
        {
            if (toPaint == null)
            {
                FillIn(map.Set);
            }

            bool ok = true;
            for (int b = 0; b < 3 && ok; ++b)
            {
                for (int a = 0; a < 3; ++a)
                {
                    if (pattern[a, b] != 0)
                    {
                        Tile t = map.GetTileAt(x + a - 1, y + b - 1);
                        bool cont = brush.Contains(t);

                        if (pattern[a, b] == -1)
                            ok = ok && !cont;
                        else
                            ok = ok && cont;

                        if (!ok) { break; }
                    }
                }
            }

            return ok;
        }

        public void ApplyTo(TileMap map, int x, int y)
        {
            if (toPaint == null)
            {
                toPaint = new List<Tile>();
                for (int i = 0; i < ids.Length; ++i)
                    toPaint.Add(map.Set.GetTile(ids[i]));
            }
            map.SetTileAt(x, y, GetTile());
        }

        public Tile GetTile()
        {
            return toPaint[Util.getRnd().Next(0, toPaint.Count)];
        }

        public int EntropyIndex
        {
            get
            {
                int i = 0;
                for (int b = 0; b < 3; ++b)
                    for (int a = 0; a < 3; ++a)
                        if (pattern[a, b] != 0) i++;
                return i;
            }
        }       
    }

    public class ManagedBrush
    {
        private List<Tile> allTiles = new List<Tile>();
        private List<BrushRule> rules = new List<BrushRule>();
        private Tile defTile = null;
        private string name = "New brush";
        private INIGroup iniDesc = null;
        private int dtileNum = -1;

        public ManagedBrush()
        {
            ManagedBrushStorage.Brushes.Add(this);
        }

        public ManagedBrush(string theName)
        {
            this.name = theName;
            ManagedBrushStorage.Brushes.Add(this);
        }

        public string Name { get { return name; } set { name = value; } }

        public override string ToString()
        {
            return name;
        }

        public bool Contains(Tile t)
        {
            return allTiles.Contains(t);
        }

        public List<BrushRule> Rules { get { return rules; } }
        public Tile DefaultTile { get { return defTile; } set { defTile = value; } }

        public void UpdateFromRules()
        {
            allTiles.Clear();
            foreach (BrushRule rule in rules)
                foreach (Tile t in rule.Tiles)
                    allTiles.Add(t);
        }

        public void LoadFromFile(string fileName, TileSet ts)
        {
            LoadFromFile(new FileStream(fileName, FileMode.Open), ts);
        }

        public void LoadFromFile(FileStream fs, TileSet ts)
        {
            INIFile ini = new INIFile(fs);
            INIGroup g = null;
            while ((g = ini.getNext()) != null)
            {
                if (g.title == "ManagedBrush")
                {
                    iniDesc = g;
                    ReadFromSource();
                    FillIn(ts);
                }
                else if (g.title == "BrushRule")
                {
                    BrushRule br = new BrushRule(this, g);
                    br.FillIn(ts);
                    rules.Add(br);
                }
            }

            UpdateFromRules();
        }

        public void SaveToFile(string fileName)
        {
            UpdateSource();
            iniDesc.AppendToFile(fileName);

            foreach (BrushRule br in rules)
            {
                br.UpdateSource();
                br.INIDescription.AppendToFile(fileName);
            }
        }

        private void ReadFromSource()
        {
            name = iniDesc.getString("Name", "New brush");
            dtileNum = iniDesc.getInteger("DefTile", -1);
            defTile = null;
        }

        public void FillIn(TileSet ts)
        {
            if (dtileNum >= 0)
                defTile = ts.GetTile(dtileNum);
            else
                defTile = null;
        }

        private void UpdateSource()
        {
            if (iniDesc == null) iniDesc = new INIGroup("ManagedBrush");
            iniDesc.set("Name", name);
            if (defTile != null)
                iniDesc.set("DefTile", defTile.ID.ToString());
            else
                iniDesc.set("DefTile", "-1");
        }

        private bool MatchRule(TileMap map, int x, int y)
        {
            if (defTile == null && dtileNum >= 0) defTile = map.Set.GetTile(dtileNum);
            Tile oldTile = map.GetTileAt(x, y);
            bool bel = Contains(oldTile);
            if (oldTile == null) bel = false;
            Tile newTile = MatchRule(map, x, y, true);
            bool bel2 = allTiles.Contains(newTile);
            return bel != bel2;
        }

        private Tile MatchRule(TileMap map, int x, int y, bool useDef)
        {
            int maxInd = 0;
            BrushRule r = null;
            if (defTile == null && dtileNum >= 0) defTile = map.Set.GetTile(dtileNum);

            map.SetTileAt(x, y, defTile);

            foreach (BrushRule rule in rules)
            {
                int ind = rule.EntropyIndex;
                if (ind > maxInd && rule.Match(map, x, y))
                {
                    maxInd = ind;
                    r = rule;
                }
            }

            if (r == null)
            {
                if (useDef)
                    return defTile;
                else
                    return null;
            }
            else
            {
                return r.GetTile();
            }
        }

        public void Paint(TileMap map, int x, int y)
        {
            LinkedList<Point> stack = new LinkedList<Point>();
            if (defTile == null && dtileNum >= 0) defTile = map.Set.GetTile(dtileNum);

            MatchRule(map, x, y);
            stack.AddLast(new Point(x, y));

            while(stack.Count > 0)
            {
                Point pt = stack.Last.Value;
                stack.RemoveLast();
                if (Contains(map.GetTileAt(pt.X, pt.Y)))
                {
                    bool chg = MatchRule(map, pt.X, pt.Y);
                    if (pt.X == x && pt.Y == y)
                    {
                        stack.AddLast(new Point(x - 1, y - 1));
                        stack.AddLast(new Point(x, y - 1));
                        stack.AddLast(new Point(x + 1, y - 1));
                        stack.AddLast(new Point(x + 1, y));
                        stack.AddLast(new Point(x + 1, y + 1));
                        stack.AddLast(new Point(x, y + 1));
                        stack.AddLast(new Point(x - 1, y + 1));
                        stack.AddLast(new Point(x - 1, y));
                    }
                }
            }
        }

        public void PaintToMapView(MapView mv, int x, int y)
        {
            LinkedList<Point> stack = new LinkedList<Point>();
            if (defTile == null && dtileNum >= 0) defTile = mv.Map.Set.GetTile(dtileNum);

            mv.PaintTile(x, y, MatchRule(mv.Map, x, y, true));
            //stack.AddLast(new Point(x, y));

            stack.AddLast(new Point(x - 1, y - 1));
            stack.AddLast(new Point(x, y - 1));
            stack.AddLast(new Point(x + 1, y - 1));
            stack.AddLast(new Point(x + 1, y));
            stack.AddLast(new Point(x + 1, y + 1));
            stack.AddLast(new Point(x, y + 1));
            stack.AddLast(new Point(x - 1, y + 1));
            stack.AddLast(new Point(x - 1, y));

            while (stack.Count > 0)
            {
                Point pt = stack.Last.Value;
                stack.RemoveLast();
                if (Contains(mv.Map.GetTileAt(pt.X, pt.Y)))
                {
                    Tile matchedTile = MatchRule(mv.Map, pt.X, pt.Y, false);
                    mv.PaintTile(pt.X, pt.Y, matchedTile);
                }
            }
        }
    }

    static class ManagedBrushStorage
    {
        private static List<ManagedBrush> brushes = new List<ManagedBrush>();

        public static List<ManagedBrush> Brushes { get { return brushes; } }
    }
}
